<?php
/** Rho form handling class.
*
* 
* Load this file with <code>require_once Rho::$PATH.'rho/Rho_Form.class.php';</code>
*
* If loading at the top of a controller file instead of inside a method,
* require it after requiring core and before the call to Rho::int();
*
* @package Rho
* @author Pete
* @version 0.1
* @license http://www.opensource.org/licenses/apache2.0.php Apache License, Version 2.0.*/

/** Class that handles form submission and generation.
*
* @package Rho
* @subpackage Rho-forms */
final class Rho_Form{
  
  private $_id;

  private $_cmd;
  
  private $_rules;
  
  private $_renderers=NULL;
  
  private $_defaults;
  
  private $_ok=FALSE;
  
  private $_method='POST';
  
  private $_enctype='application/x-www-form-urlencoded';
  
  private $_action;
  
  private $_view=NULL;
  
  private $_has_filtered=FALSE;
  
  private $_have_fixed_defaults=FALSE;

  public function __construct($base_name, $cmd, array & $rules, array & $defaults=NULL){
    $this->_cmd=$cmd;
    $this->_rules=& $rules;
    if($defaults==NULL){
      $this->_defaults=array();
    }
    else {
      $this->_defaults=& $defaults;
    }
    $this->_id=$base_name.'_'.$cmd.'_form';
    global $_SERVER;
    $this->_action=Rho::ifset($_SERVER, 'PHP_SELF', '#');
  }
  
  public static function fromModel(& $model, $base_name, $cmd){
    return new Rho_Form($base_name, $cmd, NULL);
  }
  
  public function useGET(){
    $this->_method='GET';
    $this->_enctype='application/x-www-form-urlencoded';
  }
  
  public function usePOST(){
    $this->_method='POST';
  }
  
  public function useFiles(){
    $this->_method='POST';
    $this->_enctype='multipart/form-data';
  }
  
  public function setView(string $view_name){
    $this->_view=$view_name;
  }
  
  public function getCmd(){
    return $this->_cmd;
  }
  
  public function getId(){
    return $this->_id;
  }
  
  public function setId(string $new_id){
    if(! empty($new_id) ){
      $this->_id=$new_id;
    }
  }

  public function process(& $controller, $render=TRUE){
    if($this->_method=='POST' ){
      global $_POST;
      if($_POST){ 
        $data=$_POST;
      }
      else {
        $n=NULL;
        if($render) $this->_render($controller, $n);
        return;
      }
    }
    else {
      global $_GET;
      if($_GET){ 
        $data=$_GET;
      }
      else {
        if($render) $this->_render($controller, NULL);
        return;
      }
    }
    // form submitted so process
    require_once Rho::$PATH.'rho/Rho_Valid.class.php';
    $v=new Rho_Valid($this->_rules);
    $this->_filter($controller, $data);
    if($v->valid($data) ){
      $this->_ok=TRUE;
      return;
    }
    if($render) $this->_render($controller, $data, $v->getErrors() );
    unset($v);
  }
  
  private function _filter(& $controller, & $data){
    if($this->_method!='POST' ){
      $this->_filterGET($controller, $data);
      return;
    }
    global $_POST;
    foreach($data as $key=>$value) :
      $data[$key]=$controller->post($key);
    endforeach;
    $this->_has_filtered=TRUE;
  }
  
  private function _filterGET(& $controller, & $data){
    global $_GET;
    foreach($data as $key=>$value) :
      $data[$key]=$controller->get($key);
    endforeach;
    $this->_has_filtered=TRUE;
  }
  
  private  final function _ruleName(& $key){
    $parts=explode('|', $this->_rules[$key], 2);
    return $parts[0];
  }
  
  /** Determine if the form was submitted and has validated.
  * @return bool TRUE is the form has been submitted all fields are valid, otherwise FALSE.*/
  public function isOk(){
    return $this->_ok;
  }
  
  private function _render(& $controller, & $data, & $errors=NULL){
    if($data==NULL){
      if(! $this->_have_fixed_defaults) $this->_fixDefaults();
      $data=$this->_defaults;
    }
    else {
      // make sure we have all fields
      foreach($this->rules as $key=>$rule) :
        if(! isset($data[$key]) ){
          if(! $this->_have_fixed_defaults) $this->_fixDefaults();
          $data[$key]=$this->_defaults[$key];
        }
      endforeach;
      if(! $this->_has_filtered) $this->filter($data);
    }
    // make sure fields are safe for display as form values
    $clean_data=$data;
    foreach($data as $key=>$value) :
      $clean_data[$key]=htmlspecialchars(html_entity_decode($value, ENT_QUOTES, Rho::conf('enc') ), ENT_QUOTES, Rho::conf('enc') );
    endforeach;
    // if we have a specified view, use it
    if($this->_view!=NULL){
      $controller->loadView($this->_view, array('values'=>$clean_data, 'form_errors'=>$errors) );
      return;
    }
    // ok, use the generator instead
    require_once Rho::$PATH.'rho/form_gen.inc.php';
    // get the renderers list if not already set
    if($this->_renderers==NULL ){
      $this->_renderers=$controller->_formGetRenderers($this);
    }
    RForm_Generator::generate($this->_rules, $this->_renderers, $clean_data, $this->_id, $this->_method, $this->_action, $this->_enctype);
  }
  
  private function _fixDefaults(){
    foreach($this->_rules as $key=>$rule) :
      if(! isset($this->_defaults[$key]) ){
        switch($this->_ruleName($key) ) :
          case 'string' : $this->_defaults[$key]=''; break;
          case 'int' : $this->_defaults[$key]=0; break;
          case 'float' : $this->_defaults[$key]=0.0; break;
          default: $this->_defaults[$key]=''; break;
        endswitch;
      }
    endforeach;
    $this->_have_fixed_defaults=TRUE;
  }
  
  /** @ignore */
  public function render(& $controller, $errors=NULL){
    $data=NULL;    
    if($this->_method=='POST' ){
      global $_POST;
      if($_POST){ 
        $data=$_POST;
      }
    }
    else {
      global $_GET;
      if($_GET){ 
        $data=$_GET;
      }
    }
    $this->_render($controller, $data, $errors);
  }
}

/** Interface that Controllers that use Rho_Form must implement if use automatic form generation.
*
* Controllers that use many forms should use a switch after the interface call:
* <code>
* protected funtion formGetRenderers(object & $form){
*  switch($form->getCmd() ) :
*    case 'edit': return $this->editFormRenderers(); break;
*    case 'new' : return $this->newFormRenderers(); break;
*  endswitch;
* } </code>
*
* @see Rho_Form
* @package Rho
* @subpackage Rho-forms */
interface RForm_Handler{

  /** Called when the form is to be displayed and is using the form generator.
  * @param Rho_Form $form The form object making the call.
  * @return array A list of renderers.
  * @see Rho_Form::process(), Rho_Form::render() */
   function _formGetRenderers(object & $form);
}